<template>
  <div class="drag-box"
    :key="platform"
    @[start]="handleStart"
    @[move]="handleMove"
    @[end]="handleEnd"
    @click.capture="handleClick"
    :style="style"
    ref="box">
    <slot></slot>
  </div>
</template>

<script>
import { DEFAULT_LEFT, DEFAULT_BOTTOM, MOBILE, PC, MIN_DISTANCE, eventConfig } from './config'

export default {
  name: 'vue-drag-box',
  props: {
    initLeft: {
      type: Number,
      default: DEFAULT_LEFT
    },
    initBottom: {
      type: Number,
      default: DEFAULT_BOTTOM
    }
  },
  data () {
    return {
      platform: MOBILE,
      isDown: false,
      startX: 0,
      startY: 0,
      distanceX: 0,
      distanceY: 0,
      oldLeft: 0,
      oldBottom: 0,
      left: 0,
      bottom: 0,
      width: 0,
      height: 0,
      frame: null
    }
  },
  computed: {
    isPC () {
      return this.platform === PC
    },
    start () {
      return eventConfig[this.platform].start
    },
    move () {
      return eventConfig[this.platform].move
    },
    end () {
      return eventConfig[this.platform].end
    },
    style () {
      return {
        left: `${this.left}px`,
        bottom: `${this.bottom}px`
      }
    }
  },
  watch: {
    width () {
      this.handleResize()
    },
    height () {
      this.handleResize()
    }
  },
  mounted () {
    this.handleResize()
    window.addEventListener('resize', this.handleResize)
    if (this.isPC) {
      window.addEventListener(this.move, this.handleMove)
    }
  },
  beforeDestroy () {
    this.frame && cancelAnimationFrame(this.frame)
    window.removeEventListener('resize', this.handleResize)
    if (this.isPC) {
      window.removeEventListener(this.move, this.handleMove)
    }
  },
  methods: {
    // 获取容器的大小
    getSize () {
      const box = this.$refs.box
      const width = box.offsetWidth
      const height = box.offsetHeight

      if (width !== this.width) this.width = width
      if (height !== this.height) this.height = height
      this.frame = requestAnimationFrame(this.getSize)
      if (width !== 0) {
        cancelAnimationFrame(this.frame)
      }
    },
    // 记录当前位置
    recordPosition () {
      this.oldLeft = this.left
      this.oldBottom = this.bottom
    },
    // 设置位置
    setPosition () {
      this.left = this.oldLeft + this.distanceX
      this.bottom = this.oldBottom + this.distanceY

      if (this.left > window.innerWidth - this.width) {
        this.left = window.innerWidth - this.width
      } else if (this.left < 0) {
        this.left = 0
      }

      if (this.bottom > window.innerHeight - this.height) {
        this.bottom = window.innerHeight - this.height
      } else if (this.bottom < 0) {
        this.bottom = 0
      }
    },
    // 触发点击事件
    triggerClick (target) {
      const event = new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
        button: 1,
        view: window
      })

      target.dispatchEvent(event)
      this.$emit('click')
    },
    createHandle (eventName) {
      eventName = eventName.charAt(0).toUpperCase() + eventName.slice(1)

      return `handle${eventName}`
    },
    // 窗口大小变化事件
    handleResize () {
      const isMobile = /Android|webOS|iPhone|iPod|BlackBerry/i.test(window.navigator.userAgent)

      this.platform = isMobile ? MOBILE : PC
      this.left = window.innerWidth - this.width - this.initLeft
      this.bottom = this.initBottom
      this.isDown = false
      this.recordPosition()
      this.getSize()
    },
    // 开始事件
    handleStart (event) {
      this[this.createHandle(this.start)](event)
    },
    // 移动事件
    handleMove (event) {
      this[this.createHandle(this.move)](event)
    },
    // 结束事件
    handleEnd (event) {
      this[this.createHandle(this.end)](event)
    },
    // 移动端事件
    handleTouchstart (event) {
      this.startX = event.touches[0].pageX
      this.startY = event.touches[0].pageY
    },
    handleTouchmove (event) {
      this.distanceX = event.touches[0].pageX - this.startX
      this.distanceY = this.startY - event.touches[0].pageY
      requestAnimationFrame(this.setPosition)
    },
    handleTouchend () {
      this.recordPosition()
      this.distanceX = 0
      this.distanceY = 0
    },
    // pc端事件
    handleMousedown (event) {
      event.preventDefault()
      this.startX = event.clientX
      this.startY = event.clientY
      this.isDown = true
    },
    handleMousemove (event) {
      if (!this.isDown) return
      this.distanceX = event.clientX - this.startX
      this.distanceY = this.startY - event.clientY
      requestAnimationFrame(this.setPosition)
    },
    handleMouseup (event) {
      if (!event.button && Math.abs(Math.max(this.distanceX, this.distanceY)) < MIN_DISTANCE) {
        this.triggerClick(event.target)
      }
      this.recordPosition()
      this.distanceX = 0
      this.distanceY = 0
      this.isDown = false
    },
    handleClick (event) {
      if (this.isPC && !event.button) {
        event.stopImmediatePropagation()
      }
    }
  }
}
</script>

<style scoped>
.drag-box {
  position: fixed;
  left: 0;
  bottom: 0;
  line-height: 0;
}
</style>
